package com.dns.server.primary;

import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.dns.message.DNS_Message;

/**
 * This class responsible for doing the DNS to IP or IP to DNS look up
 * it maintains a cache of recently requested lookups
 * @author Lakshmana s
 *
 */
public class Address_Lookup {

	private HashMap<String,String> DNSToIPMap=null;
	private HashMap<String,String> IPToDNSMap=null;
	private HashMap<String,Long> DNSToTTL =null;
	private static Address_Lookup instance=null;
	private ReentrantReadWriteLock readWriteLock=null;
	private Lock read=null,write=null;
	
	
	private Address_Lookup(){
		DNSToIPMap = new HashMap<String,String>();
		IPToDNSMap = new HashMap<String,String>();
		readWriteLock = new ReentrantReadWriteLock();
		DNSToTTL = new HashMap<String, Long>();
		read= readWriteLock.readLock();
		write = readWriteLock.writeLock();
		DNSToIPMap.put("test.domain.com","127.0.0.1");
		IPToDNSMap.put("127.0.0.1", "test.domain.com");
	}
	
	/**
	 * creates a single instance of Address look up
	 * @return
	 */
	public static Address_Lookup getInstance(){
		if(instance==null){
			instance = new Address_Lookup();
		}
		return instance;
	}
	
	/**
	 * this method does the look up, given the Domain name or IP address
	 * it checks the TTL and it's not expired, it return the record 
	 * @param msg
	 * @return
	 */
	public boolean lookUp(DNS_Message msg){
		read.lock();
		if(msg.getIp_address()!=null && !msg.getIp_address().equals("")){
			String ipAddress = msg.getDomainName();
			//System.out.println(this.getClass().getName()+ ": Info: Requesting for a Domain name for the IP adress : "+ipAddress);
			if(IPToDNSMap.containsKey(ipAddress)){
				String domainName =IPToDNSMap.get(ipAddress);
				if(checkTTL(domainName)){
					msg.setDomainName(domainName);
					//System.out.println(this.getClass().getName()+ ": Info: IP Addresse resolved , Domain name is :" +domainName);
					read.unlock();
					return true;
				} 
			}
		}
		else if(msg.getDomainName()!=null && !msg.getDomainName().equals("")){
			String domainName = msg.getDomainName();
			//System.out.println(this.getClass().getName()+ ": Info: Requesting for a IP lookup for the Domain name : "+domainName);
			if(DNSToIPMap.containsKey(domainName) && checkTTL(domainName)){
				String ipAddress =DNSToIPMap.get(domainName);
				msg.setIp_address(ipAddress);
				//System.out.println(this.getClass().getName()+ ": Info: Domain name resolved , IP address is :" +ipAddress);
				read.unlock();
				return true;
			}
		}
		read.unlock();
		return false;
	}
	
	/**
	 * this method updates or adds the new Domain name to IP address mapping to the cache 
	 * along with the TTL(expire time in miliseconds) for each record. 
	 * @param msg
	 * @return
	 */
	public boolean updateAddress(DNS_Message msg){
		write.lock();
		String domainName=msg.getDomainName();
		String ipAddress=msg.getIp_address();
		if(domainName==null || ipAddress==null){
			System.out.println(this.getClass().getName()+ ": Error: Invalid domain name or address in the DNS Message");
			write.unlock();
			return false;
		}
		if(DNSToIPMap.containsKey(domainName)){
			//System.out.println(this.getClass().getName()+ ": Info: Domain name exist, updating with the new IP address");
			DNSToIPMap.put(domainName, ipAddress);
		}
		else{
			System.out.println(this.getClass().getName()+ ": Info: Adding the new domain name and IP address");
			DNSToIPMap.put(domainName, ipAddress);
			
		}
		if(IPToDNSMap.containsKey(ipAddress)){
			//System.out.println(this.getClass().getName()+ ": Info: IP Address exist, updating with the new Domain name");
			IPToDNSMap.put(ipAddress,domainName);
		}
		else{
			System.out.println(this.getClass().getName()+ ": Info: Adding the new domain name and IP address");
			IPToDNSMap.put(ipAddress,domainName);
			
		}
		long ttl =msg.getTtl()*1000;
		long timestamp=System.currentTimeMillis();
		long ttlMilis= timestamp+ttl;
		DNSToTTL.put(domainName, ttlMilis);

		write.unlock();
		return true;
	}
	
	/**
	 * this methods checks the TTL of the record to see and returns true if it's not expired
	 * otherwise it returns false
	 * @param dName
	 * @return
	 */
	public boolean checkTTL(String dName){
		long ttl =DNSToTTL.get(dName);
		long curTimeMilis = System.currentTimeMillis();
		if(ttl>curTimeMilis)
			return true;
		
		return false;
	}
	
	/**
	 * this method runs periodically and removes all the DNS records whose time has expired
	 * 
	 */
	public void removeExpired(){
		long currentTimeMilis = System.currentTimeMillis();
		for(String name: DNSToTTL.keySet()){
			long dns_ttl = DNSToTTL.get(name);
			if(currentTimeMilis>dns_ttl){
				write.lock();
				String ip = DNSToIPMap.get(name);
				DNSToIPMap.remove(name);
				IPToDNSMap.remove(ip);
				DNSToTTL.remove(name);
				write.unlock();
				System.out.println(this.getClass().getName() +" : Info : Remvoing entry for "+name+" from cache, TTL expired ");
			}
		}
	}
}
